using LightCAD.MathLib;
using System;
using System.Collections;
using System.Collections.Generic;


namespace LightCAD.Three
{
    public class LatheGeometry : BufferGeometry
    {
        public class Parameters
        {
            public ListEx<Vector2> points;
            public int segments;
            public double phiStart;
            public double phiLength;
        }
        #region Properties

        public Parameters parameters;

        #endregion

        #region constructor
        public LatheGeometry(ListEx<Vector2> points = null, int segments = 12, double phiStart = 0, double phiLength = Math.PI * 2)
        {
            if (points == null)
            {
                points = new ListEx<Vector2>() { new Vector2(0, -0.5), new Vector2(0.5, 0), new Vector2(0, 0.5) };
            }
            this.type = "LatheGeometry";
            this.parameters = new Parameters()
            {
                points = points,
                segments = segments,
                phiStart = phiStart,
                phiLength = phiLength
            };
            //segments = (int)Math.Floor(segments);
            // clamp phiLength so it"s in range of [ 0, 2PI ]
            phiLength = MathEx.Clamp(phiLength, 0, MathEx.PI * 2);
            // buffers
            var indices = new ListEx<int>();
            var vertices = new ListEx<double>();
            var uvs = new ListEx<double>();
            var initNormals = new ListEx<double>();
            var normals = new ListEx<double>();
            // helper variables
            var inverseSegments = 1.0 / segments;
            var vertex = new Vector3();
            var uv = new Vector2();
            var normal = new Vector3();
            var curNormal = new Vector3();
            var prevNormal = new Vector3();
            double dx = 0;
            double dy = 0;
            // pre-compute normals for initial "meridian"
            for (int j = 0; j <= (points.Length - 1); j++)
            {
                if (j == 0) // special handling for 1st vertex on path
                {
                    dx = points[j + 1].X - points[j].X;
                    dy = points[j + 1].Y - points[j].Y;
                    normal.X = dy * 1.0;
                    normal.Y = -dx;
                    normal.Z = dy * 0.0;
                    prevNormal.Copy(normal);
                    normal.Normalize();
                    initNormals.Push(normal.X, normal.Y, normal.Z);
                }
                else if (j == points.Length - 1) // special handling for last Vertex on path
                {
                    initNormals.Push(prevNormal.X, prevNormal.Y, prevNormal.Z);
                }
                else // default handling for all vertices in between
                {
                    dx = points[j + 1].X - points[j].X;
                    dy = points[j + 1].Y - points[j].Y;
                    normal.X = dy * 1.0;
                    normal.Y = -dx;
                    normal.Z = dy * 0.0;
                    curNormal.Copy(normal);
                    normal.X += prevNormal.X;
                    normal.Y += prevNormal.Y;
                    normal.Z += prevNormal.Z;
                    normal.Normalize();
                    initNormals.Push(normal.X, normal.Y, normal.Z);
                    prevNormal.Copy(curNormal);
                }
            }
            // generate vertices, uvs and normals
            for (int i = 0; i <= segments; i++)
            {
                var phi = phiStart + i * inverseSegments * phiLength;
                var sin = Math.Sin(phi);
                var cos = Math.Cos(phi);
                for (int j = 0; j <= (points.Length - 1); j++)
                {
                    // vertex
                    vertex.X = points[j].X * sin;
                    vertex.Y = points[j].Y;
                    vertex.Z = points[j].X * cos;
                    vertices.Push(vertex.X, vertex.Y, vertex.Z);
                    // uv
                    uv.X = i / (double)segments;
                    uv.Y = j / (double)(points.Length - 1);
                    uvs.Push(uv.X, uv.Y);
                    // normal
                    var x = initNormals[3 * j + 0] * sin;
                    var y = initNormals[3 * j + 1];
                    var z = initNormals[3 * j + 0] * cos;
                    normals.Push(x, y, z);
                }
            }
            // indices
            for (int i = 0; i < segments; i++)
            {
                for (int j = 0; j < (points.Length - 1); j++)
                {
                    var baseLen = j + i * points.Length;
                    var a = baseLen;
                    var b = baseLen + points.Length;
                    var c = baseLen + points.Length + 1;
                    var d = baseLen + 1;
                    // faces
                    indices.Push(a, b, d);
                    indices.Push(c, d, b);
                }
            }
            // build geometry
            this.setIndex(indices.ToArray());
            this.setAttribute("position", new Float32BufferAttribute(vertices.ToArray(), 3));
            this.setAttribute("uv", new Float32BufferAttribute(uvs.ToArray(), 2));
            this.setAttribute("normal", new Float32BufferAttribute(normals.ToArray(), 3));
        }
        #endregion

        #region methods
        public LatheGeometry copy(LatheGeometry source)
        {
            base.copy(source);
            this.parameters = new Parameters()
            {
                points = source.parameters.points,
                segments = source.parameters.segments,
                phiStart = source.parameters.phiStart,
                phiLength = source.parameters.phiLength,
            };
            return this;
        }

        #endregion
    }
}
